/**
 *   Copyright 2005 Open Cloud Ltd.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package org.mobicents.eclipslee.ant;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.util.FileUtils;


/**
 SleeJar defines common behaviour amongst all the various types of deployable jars.
 Specifically construction of classpaths, properties that define whether the task should
 use deployment descriptor info to deduce what classes should be included etc.
 <p>
 A default name for the deployment descriptor is assumed if one is not provided, which means
 in the common case the 'foo'jarxml property can safely be ommitted. This task also defines
 an extxml property to allow oc extension descriptors to be defined.
*/

public abstract class SleeJar extends org.apache.tools.ant.taskdefs.Jar implements Component {
    public SleeJar(String archiveType, String emptyBehaviour) {
        super();
        this.archiveType = archiveType;
        this.emptyBehavior = emptyBehavior;
        this.jarXmlStr = archiveType + ".xml";
    }

    public void setMetainfbase(String metainfbase) {
        this.metainfbase = new File(metainfbase);
    }

    public void setMetaInfBase(File metainfbase) {
        this.metainfbase = metainfbase;
    }

    // Component interface
    public File getComponentFile(Project project) throws BuildException {
        if (generateName && zipFile == null) {
            // Use an autogenerated name.
            try {
                zipFile = File.createTempFile(getComponentType(), ".jar");
                zipFile.deleteOnExit();

		// Create the ZIP file entry table, else the ANT Jar task
		// grumbles loudly and generates lots of spam warning
		// messages when it tries to open a completely empty file
		// as a ZIP file.

		ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));

		// Add an empty directory - ZIP files must have at least one
		// entry.
		ZipEntry ze = new ZipEntry("META-INF/");
		ze.setSize (0);
		ze.setMethod (ZipEntry.STORED);
		// An empty CRC for the directory.
		ze.setCrc(new CRC32().getValue());
		zos.putNextEntry(ze);
		zos.close();

            } catch (IOException ioe) {
                throw new BuildException(ioe);
            }
        }

        return zipFile;
    }

    // Main execution entry point.
    public void execute() throws BuildException {

        if (autoinclude && super.isInUpdateMode())
            throw new BuildException("update mode not supported when autoinclude=true");

        if (null == jarXmlStr)
            throw new BuildException(getJarXmlName() + " attribute is required", getLocation());

        if (autoinclude) {
            includeClasses();
            autoinclude = false; // On subsequent executions, don't add them again.
        }

        /* (discard) */ getComponentFile(getProject()); // Ensure zipFile is set.
        super.execute();

    }

    // Setters:
    //   classpath="..."
    public void setClasspath(Path newClasspath) {
        if (this.classpath == null)
            this.classpath = newClasspath;
        else
            this.classpath.append(newClasspath);
    }

    //   nested <classpath>
    public Path createClasspath() {
        if (classpath == null)
            classpath = new Path(getProject());

        return classpath.createPath();
    }

    //   classpathref="..."
    public void setClasspathRef(Reference r) {
        createClasspath().setRefid(r);
    }

    public void setGeneratename(boolean onoff) {
        this.generateName = onoff;
    }

    //   autoinclude="..."
    public void setAutoinclude(boolean onoff) {
        this.autoinclude = onoff;
    }

    // subclasses implement these operations
    protected abstract void includeTypeSpecificClasses() throws BuildException;
    protected abstract String getComponentType();
    protected abstract String getJarXmlName();

    private void includeClasses() throws BuildException {
        if (classpathFileList == null) {
            // Initialize the classpath once.

            if (classpath == null)
                throw new BuildException("autoinclude set, but classpath attribute not set");
            
            String[] classpathList = classpath.list();
            classpathFileList = new File[classpathList.length];
            classpathZipList = new ZipFile[classpathList.length];
            for (int i = 0; i < classpathList.length; ++i)
                classpathFileList[i] = fileUtils.normalize(classpathList[i]);
        }

        processJarXml();

        includeTypeSpecificClasses();
    }

    protected final void includeClass(String className) throws BuildException {
        String osPath = className.replace('.', File.separatorChar) + ".class";
        String urlPath = className.replace('.', '/') + ".class";

        // Search classpath elements.
        for (int i = 0; i < classpathFileList.length; ++i) {
            File cpBase = classpathFileList[i];
            
            if (!cpBase.exists())
                continue; // Missing.

            if (cpBase.isDirectory()) {
                File testFile = fileUtils.resolveFile(cpBase, osPath);
                if (testFile.exists()) {
                    // Found it as a .class file
                    FileSet fileSet = new FileSet();
                    fileSet.setDir(cpBase);
                    fileSet.setIncludes(osPath);
                    super.addFileset(fileSet);
                    return;
                }

                continue;
            }

            if (cpBase.isFile()) {
                // Assume it's a zip/jarfile.

                if (classpathZipList[i] == null) {
                    // Initialize the zipfile once.

                    try {
                        classpathZipList[i] = new ZipFile(cpBase);
                    } catch (IOException ioe) {
                        // Can't read the zipfile, ignore it.
                        classpathZipList[i] = null;
                        continue;
                    }
                }

                if (classpathZipList[i].getEntry(urlPath) != null) {
                    // Found it within a .zip/.jar
                    ZipFileSet zipFileSet = new ZipFileSet();
                    zipFileSet.setSrc(classpathFileList[i]);
                    zipFileSet.setIncludes(urlPath);
                    super.addZipfileset(zipFileSet);
                    return;
                }

                continue;
            }

            // unknown classpath entry -- ignore it.
        }

        throw new BuildException("Cannot locate class in classpath: " + className);
    }

    // subclasses will define jar specific 'foojarxml' attributes
    protected final void setJarXml(String jarXmlStr) {
        this.jarXmlStr = jarXmlStr;
    }

    public final void setExtjarxml(String extJarXmlStr) {
        this.extJarXmlStr = extJarXmlStr;
    }
   
    private void processJarXml() {
        // process the normal descriptor
        jarxml = getAbsoluteFile((null == metainfbase) ? new File(jarXmlStr) : new File(metainfbase, jarXmlStr));
        
        processDescriptor(jarxml, archiveType + ".xml");

        if (null != extJarXmlStr) {
            // process the ext descriptor if we have one
            File extXml = getAbsoluteFile((null == metainfbase) ? new File(extJarXmlStr) : new File(metainfbase, extJarXmlStr));

            processDescriptor(extXml, extXml.getName());
        }
    }

    private void processDescriptor(File xmlDtor, String xmlDtorName) {
        if (!xmlDtor.exists())
            throw new BuildException("Deployment descriptor: " + xmlDtor + " does not exist.");

        // Add a new fileset for the DD.
        ZipFileSet fs = new ZipFileSet();
        fs.setFile(xmlDtor);
        fs.setFullpath("META-INF/" + xmlDtorName);
        super.addFileset(fs);
    }

    protected void cleanUp() {
        // Clean up zipfiles 
        if (classpathZipList != null) {
            for (int i = 0; i < classpathZipList.length; ++i) {
                if (classpathZipList[i] != null) {
                    try { classpathZipList[i].close(); }
                    catch (IOException e) {}
                }
            }
            
            classpathFileList = null;
            classpathZipList = null;
        }
    }

    protected File getAbsoluteFile(File file){
        //turn into an absolute file
        if (!file.isAbsolute()) 
            return new File(getProject().getProperty("basedir"),file.toString());
        else
            return file;
    }

//  protected final File getMetaInfBase() { return metainfbase; }
    protected final File getJarXml() { return jarxml; }

    private String jarXmlStr;
    private String extJarXmlStr;
    private File metainfbase = null;
    private File jarxml = null;
    private Path classpath;

    private boolean autoinclude = true;
    private boolean generateName;

    private File[] classpathFileList;
    private ZipFile[] classpathZipList;

    protected static final FileUtils fileUtils = FileUtils.newFileUtils();
    protected static final SleeDTDResolver entityResolver = new SleeDTDResolver();
}
